﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Rhino.Mocks;

namespace T4EditorClassifier.Tests
{
    public static class ClassificationTestHelper
    {
        static ITextSnapshotLine SetupSnapshotLine(ITextSnapshot snapshot,int position, string data, int lineNumber)
        {
            var result = MockRepository.GenerateStub<ITextSnapshotLine>();
            result.Stub(f => f.End).Return(new SnapshotPoint(snapshot, position + result.Length));
            result.Stub(f => f.EndIncludingLineBreak).Return(new SnapshotPoint(snapshot, position + result.LengthIncludingLineBreak));

            result.Stub(f => f.Extent).Return(new SnapshotSpan(snapshot, new Span(position, result.Length)));
            result.Stub(f => f.ExtentIncludingLineBreak).Return(new SnapshotSpan(snapshot, new Span(position, result.LengthIncludingLineBreak)));

            result.Stub(s => s.GetText()).Return(data.TrimEnd('\r', '\n'));
            result.Stub(s => s.GetTextIncludingLineBreak()).Return(data);

            result.Stub(s => s.Length).Return(data.TrimEnd('\r', '\n').Length);
            result.Stub(s => s.LengthIncludingLineBreak).Return(data.Length);

            result.Stub(f => f.LineBreakLength).Return(Regex.Match(data, "\r?\n?").Length);

            result.Stub(s => s.LineNumber).Return(lineNumber);
            
            result.Stub(s => s.Snapshot).Return(snapshot);
            result.Stub(f => f.Start).Return(new SnapshotPoint(snapshot, position));
            return result;
        }
        public static ITextSnapshot SetupSnapshot(string input)
        {
            var lines = input.SplitLinesPreserve();

            
            var snapshot = MockRepository.GenerateStub<ITextSnapshot>();
            snapshot.Stub(s => s.LineCount).Return(lines.Length);
            snapshot.Stub(s => s.Length).Return(input.Length);
            snapshot.Stub(s => s.GetLineFromLineNumber(0)).IgnoreArguments().Return(null).WhenCalled(i =>
            {
                var lineNumber= (int)i.Arguments[0];
                var position = lines.Take(lineNumber ).SelectMany(s => s.ToCharArray()).Count();
                var sLine = SetupSnapshotLine(snapshot,position, lines[lineNumber], lineNumber);

                i.ReturnValue= sLine;


            });
            snapshot.Stub(s => s.GetLineNumberFromPosition(0)).IgnoreArguments().Return(-1).WhenCalled(i =>
            {
                var x =(int) i.Arguments[0];
                if (x == 0 || lines[0].Length>=x)
                {
                    i.ReturnValue = 0;
                    return;
                }
                var lineNumber=1;
                while(lineNumber<lines.Length)
                {
                    if(lines.Take(lineNumber+1).SelectMany(c=>c.ToCharArray()).Count()>=x)
                    {
                        i.ReturnValue=lineNumber;
                        return;
                    }
                    lineNumber++;
                }
                throw new ArgumentOutOfRangeException();    
                    
            });

            snapshot.Stub(s => s.GetLineFromPosition(0)).IgnoreArguments().Return(null).WhenCalled(i=>{
                var pos =(int) i.Arguments[0];
                if (pos > input.Length || pos < 0)
                    throw new ArgumentOutOfRangeException();

                if (lines.Length == 1 )
                {
                    i.ReturnValue = SetupSnapshotLine(snapshot,0,input,0);
                    return;
                }

                int fullPosition = pos;
                var lineStart=0;
                var lineEnd=0;

                for (int x = 0; x < lines.Length; x++)
                {
                    lineEnd+=lines[x].Length;
                    if (fullPosition < lines[x].Length)
                    {
                       var result= SetupSnapshotLine(snapshot, lineStart, lines[x], x);
                        
                        i.ReturnValue = result;

                        return;
                    }
                    fullPosition -= lines[x].Length;
                    lineStart+=lines[x].Length;
                    
                }
                throw new ArgumentOutOfRangeException();
            });
            // snapshot.Stub(s => s.GetLineFromLineNumber(0)).IgnoreArguments().Return(snapshotLine);
            //snapshot.Stub(s => s.GetText()).Return(source);

            //failure cause?
            //snapshot.Stub(r => r.GetText()).IgnoreArguments().Return(input);
            snapshot.Stub(r => r.GetText()).Return(input);
            snapshot.Stub(r => r.GetText(Arg<int>.Is.Anything, Arg<int>.Is.Anything)).Return(input)
                .WhenCalled(i =>
                {
                    i.ReturnValue = input.Substring((int)i.Arguments[0], (int)i.Arguments[1]);
                });
            snapshot.Stub(r => r.GetText(Arg<Span>.Is.TypeOf)).Return(input).WhenCalled(i =>
            {
                Span s = (Span)i.Arguments.First();
                i.ReturnValue = input.Substring(s.Start, s.Length);
            });

            //snapshot.Stub(s => s.LineCount).Return(1);
           
            return snapshot;
        }
        public static IClassifier SetupClassifier(Func<IClassificationTypeRegistryService, IClassifier> constructor)
        {
            var cType = MockRepository.GenerateStub<IClassificationType>();
            var mockregistry = MockRepository.GenerateStub<Microsoft.VisualStudio.Text.Classification.IClassificationTypeRegistryService>();
            mockregistry.Stub(r => r.GetClassificationType(string.Empty)).IgnoreArguments().Return(cType);
            var outputClassifier = constructor(mockregistry);
            return outputClassifier;
        }
        public static  IList<ClassificationSpan> GetClassificationSpans(string input,Func<IClassificationTypeRegistryService,IClassifier> constructor)
        {
            var outputClassifier = SetupClassifier(constructor);
            var snapshot = SetupSnapshot(input);


            var test = snapshot.GetText();


            var snapshotSpan = new SnapshotSpan(snapshot, 0, input.Length);
            var line = snapshotSpan.Start.GetContainingLine();
            if (input.Contains('\r') == false && input.Contains('\n') == false)
                Assert.AreEqual(input, line.GetText());
            return outputClassifier.GetClassificationSpans(snapshotSpan);
        }
        }
    }
